namespace SS.Common.Lock
{
    #region Used namespaces

    using System;
    using System.Threading;

    #endregion

    /// <summary>
    /// Wait by spinning CPU
    /// </summary>
    public class SpinWait
    {
        /// <summary>
        /// The number of STEP until SpinOnce yield on multicore machine 
        /// </summary>
        private const int STEP = 10;

        /// <summary>
        /// Check wheter we are running on single CPU
        /// </summary>
        private static readonly bool IsSingleCpu = Environment.ProcessorCount == 1;

        /// <summary>
        /// Number of spinning times
        /// </summary>
        private int ntime;

        /// <summary>
        /// Try to spin one time
        /// </summary>
        public void SpinOnce()
        {
            // On a single-CPU system, spinning does no good
            if (IsSingleCpu)
            {
                Yield();
            }
            else
            {
                if (Interlocked.Increment(ref this.ntime) % STEP == 0)
                {
                    Yield();
                }
                else
                {
                    // Multi-CPU system might be hyper-threaded, let other thread run
                    Thread.SpinWait(10);
                }
            }
        }

        /// <summary>
        /// Yield using Sleep(0)
        /// </summary>
        private static void Yield()
        {
            // Replace by Thread.Sleep(0) which does almost the same thing
            // (going back in kernel mode and yielding) but avoid the branching and unmanaged bridge
            Thread.Sleep(0);
        }

        /// <summary>
        /// Reset number of spinning time
        /// </summary>
        public void Reset()
        {
            this.ntime = 0;
        }

        /// <summary>
        /// Is next spin will yield
        /// </summary>
        public bool NextSpinWillYield
        {
            get
            {
                return IsSingleCpu ? true : this.ntime % STEP == 0;
            }
        }

        /// <summary>
        /// Spinning times count
        /// </summary>
        public int Count
        {
            get
            {
                return this.ntime;
            }
        }
    }
}
